package com.lunasoft.nettyuploaddemo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.codec.http.multipart.*;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.Date;
import java.util.Set;

/**
 * @author Jierry
 * @version 2016/7/7.
 */
public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    private static final Logger log = LoggerFactory.getLogger(HttpUploadServerHandler.class);
    private HttpPostRequestDecoder decoder;
    private HttpRequest request;
    private final StringBuilder responseContent = new StringBuilder();
    private static final HttpDataFactory factory =
            new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // Disk if size exceed
    private boolean readingChunks;

    static {
        DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file on exit (in normal exit)
        DiskFileUpload.baseDirectory = null; // system temp directory
        DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on exit (in normal exit)
        DiskAttribute.baseDirectory = null; // system temp directory
    }

    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
        if (httpObject instanceof HttpRequest){
            HttpRequest httpRequest = (HttpRequest)httpObject;
            doHttpRequest(channelHandlerContext,httpRequest);
        }else if(httpObject instanceof HttpContent){
            HttpContent httpContent = (HttpContent)httpObject;
            doHttpContent(channelHandlerContext,httpContent);
        }else{
            log.error("Unknown http object");
        }
    }

    private void doHttpContent(ChannelHandlerContext channelHandlerContext, HttpContent httpContent) throws URISyntaxException {
        URI uri = new URI(request.uri());
        if (uri.getPath().startsWith("/api/hello")){
            ByteBuf buf = httpContent.content();
            String contentStr = null;
            if (buf.isReadable()){
                contentStr = buf.toString(CharsetUtil.UTF_8);
            }
            String result = "content: " + contentStr +
                    "\r\n hello,client,now is:"+new Date().getTime();
            responseContent.append(result);
            writeResponse(channelHandlerContext.channel());
        } else if (uri.getPath().startsWith("/api/upload")) {
            decoder = new HttpPostRequestDecoder(factory,request);
            if (decoder != null) {
                // New chunk is received
                try {
                    decoder.offer(httpContent);
                } catch (HttpPostRequestDecoder.ErrorDataDecoderException e1) {
                    e1.printStackTrace();
                    responseContent.append(e1.getMessage());
                    writeResponse(channelHandlerContext.channel());
                    channelHandlerContext.channel().close();
                    return;
                }
                responseContent.append('o');
                // example of reading chunk by chunk (minimize memory usage due to
                // Factory)
                readHttpDataChunkByChunk();
                log.info("readHttpDataChunkByChunk");
                // example of reading only if at the end
                if (httpContent instanceof LastHttpContent) {
                    responseContent.setLength(0);
                    responseContent.append("200 ok");
                    writeResponse(channelHandlerContext.channel());
                    readingChunks = false;

                    reset();

                }
            } else {
                writeResponse(channelHandlerContext.channel());
            }

        }else{
            log.error("Unknown Path: " + uri.getPath());
            responseContent.append("Unknown Path: ").append(uri.getPath());
            writeResponse(channelHandlerContext.channel());
        }
    }

    private void readHttpDataChunkByChunk() {
        while (decoder.hasNext()) {
            InterfaceHttpData data = decoder.next();
            try {
                // new value
                writeHttpData(data);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                data.release();
            }
        }

    }

    private void writeHttpData(InterfaceHttpData data) throws IOException {
        log.info("HTTP DATA name - " + data.getHttpDataType().name());

        if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
            Attribute attribute = (Attribute) data;
            String name = attribute.getName();
            String value = attribute.getValue();
            log.info("name - " + name + ", value - " + value);

        } else if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) {
            FileUpload fileUpload = (FileUpload) data;
            if (fileUpload.isCompleted()) {

                log.info("data - " + data);
                log.info("File name: " + fileUpload.getFilename()+", length - "+fileUpload.length());
                log.info("File isInMemory - " + fileUpload.isInMemory());

                log.info("File rename to ...");

                File dest = new File(new File("E:\\temp"), fileUpload.getFile().getName());
                fileUpload.renameTo(dest);
                decoder.removeHttpDataFromClean(fileUpload);

                log.info("File rename over .");
            }else {
                log.debug("File to be continued!");
            }

        }

    }

    private void reset() {
        request = null;

        // destroy the decoder to release all resources
        decoder.destroy();
        decoder = null;

    }

    private void doHttpRequest(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) throws URISyntaxException {
        HttpRequest request = this.request = httpRequest;
        HttpMethod httpMethod = request.method();
        if (httpMethod.equals(HttpMethod.GET)){
            doGet(channelHandlerContext,request);
        }else if(httpMethod.equals(HttpMethod.POST)){
            doPost(channelHandlerContext,request);
        }else{
            responseContent.setLength(0);
            responseContent.append(httpMethod.name()).append(" method not support!");
            writeResponse(channelHandlerContext.channel());
            log.error(responseContent.toString());
        }
    }

    private void doPost(ChannelHandlerContext channelHandlerContext, HttpRequest request) throws URISyntaxException {
        try {
            decoder = new HttpPostRequestDecoder(factory,request);
        }catch (HttpPostRequestDecoder.ErrorDataDecoderException e) {
            e.printStackTrace();
            responseContent.append(e.getMessage());
            writeResponse(channelHandlerContext.channel());
            channelHandlerContext.channel().close();
        }

    }

    private void doGet(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) {
        responseContent.setLength(0);
        responseContent.append(httpRequest.method().name()).append(" method not support!");
        writeResponse(channelHandlerContext.channel());
    }



    private void writeResponse(Channel channel) {
        ByteBuf buf = Unpooled.copiedBuffer(responseContent.toString(),CharsetUtil.UTF_8);
        responseContent.setLength(0);
        boolean close = HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(request.headers().get(HttpHeaderNames.CONNECTION))
                || request.protocolVersion().equals(HttpVersion.HTTP_1_0)
                && !HttpHeaderValues.KEEP_ALIVE.contentEqualsIgnoreCase(request.headers().get(HttpHeaderNames.CONNECTION));
        FullHttpResponse response = new DefaultFullHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
        if (!close) {
            // There's no need to add 'Content-Length' header
            // if this is the last response.
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
        }
        Set<io.netty.handler.codec.http.cookie.Cookie> cookies;
        String value = request.headers().get(HttpHeaderNames.COOKIE);
        if (value == null) {
            cookies = Collections.emptySet();
        } else {
//            cookies = CookieDecoder.decode(value);
            cookies = ServerCookieDecoder.STRICT.decode(value);
        }
        if (!cookies.isEmpty()) {
            // Reset the cookies if necessary.
            for (io.netty.handler.codec.http.cookie.Cookie cookie : cookies) {
//                response.headers().add(SET_COOKIE, ServerCookieEncoder.encode(cookie));
                response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie));
            }
        }
        // Write the response.
        ChannelFuture future = channel.writeAndFlush(response);
        // Close the connection after the write operation is done if necessary.
        if (close) {
            future.addListener(ChannelFutureListener.CLOSE);
        }

    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        if (decoder != null) {
            decoder.cleanFiles();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info(responseContent.toString(),cause);
        ctx.channel().close();
    }
}
